//
// Created by hamlet on 2022/1/18.
//

#ifndef SNAKE_GAME_SNAKEGAME_H
#define SNAKE_GAME_SNAKEGAME_H

#include <unordered_map>
#include <QQueue>
#include <QWidget>

class NewFruitMessage;
class PlayerInfo;
class RoomInfo;
class SnakeDirectionChangeMessage;

class Fruit {
public:
    explicit Fruit(NewFruitMessage *fruit_message);
    ~Fruit();
    const int p_ateTime;
    const NewFruitMessage *p_fruitMessage;
};

class Snake {
public:
    Snake(const PlayerInfo *player, QSize world_size, QPoint start_p, QPoint end_p);
    ~Snake();
    bool advance();
    bool isInside(const QPoint &p) const;
    [[nodiscard]] bool isSelfInside() const;
    void die();
    [[nodiscard]] bool isLiving() const { return m_dieTime == -1; }
    [[nodiscard]] const QPoint &getHead() const { return m_body.front(); }
    void addEffectiveFruit(NewFruitMessage *fruit);
    void paint(QPainter *painter, const QPoint &base_point, qreal block_size);

    const PlayerInfo *p_player;

private:
    QSize          m_worldSize;
    QQueue<QPoint> m_body;
    QPoint         m_tail;
    QPoint         m_velocity_head = F;
    QPoint         m_velocity_tail = F;
    int            m_dieTime = -1;
    static constexpr int DYING_TIME = 10;
    QVector<Fruit*> m_effectiveFruits;  // keep ownership

    static constexpr QPoint P = QPoint{0, -1};
    static constexpr QPoint N = QPoint{0, 1};
    static constexpr QPoint B = QPoint{-1, 0};
    static constexpr QPoint F = QPoint{1, 0};

    void makeAnchorPoint() { m_body.push_front(m_body.front()); }

public:
    enum class Direction : char8_t {
        P=1<<0, N=1<<1, B=1<<2, F=1<<3,
        None=0
    };
    void setDirection(Direction dir);
    bool checkDirection(Direction dir);

    Direction p_nextDirection = Direction::None;
};

class WorldWidget : public QWidget {
Q_OBJECT
public:
    using QWidget::QWidget;
    void setRoom(const RoomInfo *room);
    ~WorldWidget() override;

    void addSnake(const PlayerInfo *player);
    void setSnakeDirection(SnakeDirectionChangeMessage *message);
    bool checkDirection(qint64 id, Snake::Direction dir);

    void snakeOut(qint64 id);

    void startGame(int time_stamp);
    void stopGame();
    bool isGaming() const { return m_timerID; }

    bool p_config_isServer = false;
    void client_newFruit(NewFruitMessage *message);

signals:
    void sign_snakeCrashed(qint64 id);
    void sign_server_newFruit(NewFruitMessage *message);

protected slots:
    void timerEvent(QTimerEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *) override;

private:
    int m_timerID = 0;
    int m_timerID_fruit = 0;
    int m_timeStamp = 0;
    std::unordered_map<qint64, Snake*> m_snakes;
    QList<NewFruitMessage*> m_fruitList;  // keep ownership
    const RoomInfo *m_room = nullptr;

    QSize  m_worldSize;
    QRect  m_canvasRect;
    QPoint m_basePoint;
    qreal  m_blockSize = 0.0;

    void snakeCrashed(qint64 id);

    static constexpr int MARGIN = 10;

public:
    static int TIME;
    static constexpr int STEP = 7;
    static constexpr int TIME_STEP = 1<<STEP;  // 128ms
    static constexpr int TIME_STEP_FRUIT = 1000;
};

#endif //SNAKE_GAME_SNAKEGAME_H
